//---------------------------------------------------------------------
//  This file is part of the Background Motion solution.
// 
//  Copyright (C) Mindscape (TM).  All rights reserved.
//  http://www.mindscape.co.nz
// 
//  THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//---------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Expressions;
using System.Query;
using System.Runtime.Serialization;
using System.Text;

using Microsoft.Practices.EnterpriseLibrary.Validation;

using Mindscape.BackgroundMotion.Core.Properties;
using Mindscape.BackgroundMotion.Core.Utilities;

namespace Mindscape.BackgroundMotion.Core.Model
{
  /// <summary>
  /// An exception which indicates a validation error has occurred
  /// </summary>
  public class ValidationException : Exception
  {
    /// <summary>
    /// Initializes a new instance of the <see cref="ValidationException"/> class.
    /// </summary>
    /// <param name="message">The message.</param>
    public ValidationException(string message) : base(message)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ValidationException"/> class.
    /// </summary>
    public ValidationException()
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ValidationException"/> class.
    /// </summary>
    /// <param name="message">The message.</param>
    /// <param name="innerException">The inner exception.</param>
    public ValidationException(string message, Exception innerException) : base(message, innerException)
    {
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="ValidationException"/> class.
    /// </summary>
    /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo"></see> that holds the serialized object data about the exception being thrown.</param>
    /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext"></see> that contains contextual information about the source or destination.</param>
    /// <exception cref="T:System.Runtime.Serialization.SerializationException">The class name is null or <see cref="P:System.Exception.HResult"></see> is zero (0). </exception>
    /// <exception cref="T:System.ArgumentNullException">The info parameter is null. </exception>
    public ValidationException(SerializationInfo info, StreamingContext context) : base(info, context)
    {
    }
  }

  /// <summary>
  /// Decorator to be applied to Repositories which contain an IIdentifiable entity. The decorator manages validation of entities prior to them being added to the Repository to confirm correctness.
  /// </summary>
  /// <typeparam name="T">The generic type of the entity being persisted</typeparam>
  public class ValidationRepositoryDecorator<T> : IRepository<T>
    where T : IIdentifiable
  {
    private readonly IRepository<T> _innerRepository;

    public ValidationRepositoryDecorator(IRepository<T> innerRepository)
    {
      Invariant.ArgumentNotNull(innerRepository, "innerRepository");

      _innerRepository = innerRepository;
    }

    /// <summary>
    /// Returns a count of the items in the repository
    /// </summary>
    /// <returns></returns>
    public int Count()
    {
      return _innerRepository.Count();
    }

    /// <summary>
    /// Returns a count of the items in the repository which match the supplied lambda
    /// </summary>
    /// <param name="expression">A lambda expression which will be evaluated against the repository</param>
    /// <returns></returns>
    public int Count(Expression<Func<T, bool>> expression)
    {
      return _innerRepository.Count(expression);
    }

    /// <summary>
    /// Adds a new entity in to the repository
    /// </summary>
    /// <param name="entity"></param>
    public void Add(T entity)
    {
      Validate(entity);

      _innerRepository.Add(entity);
    }

    /// <summary>
    /// Removes the specified entity from the repository
    /// </summary>
    /// <param name="entity"></param>
    public void Remove(T entity)
    {
      _innerRepository.Remove(entity);
    }

    /// <summary>
    /// Saves the specified entity back to the repository
    /// </summary>
    /// <param name="entity"></param>
    public void Save(T entity)
    {
      Validate(entity);

      _innerRepository.Save(entity);
    }

    /// <summary>
    /// Finds a single instance of an entity matching a unique identifier.
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public T FindOne(int id)
    {
      return _innerRepository.FindOne(id);
    }

    /// <summary>
    /// Finds a single instance of an entity matching a lambda expression
    /// </summary>
    /// <param name="expression"></param>
    /// <returns>
    /// The first entity which matches the expession
    /// </returns>
    public T FindOne(Expression<Func<T, bool>> expression)
    {
      return _innerRepository.FindOne(expression);
    }

    /// <summary>
    /// Tries the find one.
    /// </summary>
    /// <param name="expression">The expression.</param>
    /// <param name="entity">The entity.</param>
    /// <returns></returns>
    public bool TryFindOne(Expression<Func<T, bool>> expression, out T entity)
    {
      return _innerRepository.TryFindOne(expression, out entity);
    }

    /// <summary>
    /// Finds all entities in the repository
    /// </summary>
    /// <returns></returns>
    public IList<T> FindAll()
    {
      return _innerRepository.FindAll();
    }

    /// <summary>
    /// Finds all entities matching a lambda expression
    /// </summary>
    /// <param name="expression">The expression.</param>
    /// <returns></returns>
    public IList<T> FindAll(Expression<Func<T, bool>> expression)
    {
      return _innerRepository.FindAll(expression);
    }

    /// <summary>
    /// Returns a requeryable set of all entities in the repository
    /// </summary>
    /// <returns></returns>
    public IQueryable<T> Find()
    {
      return _innerRepository.Find();
    }

    /// <summary>
    /// Returns a requeryable set of all entities in the repository matching a unique identifier
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    public IQueryable<T> Find(int id)
    {
      return _innerRepository.Find(id);
    }

    /// <summary>
    /// Returns a requeryable set of all entities in the repository matching a lambda expression
    /// </summary>
    /// <param name="expression"></param>
    /// <returns></returns>
    public IQueryable<T> Find(Expression<Func<T, bool>> expression)
    {
      return _innerRepository.Find(expression);
    }

    /// <summary>
    /// Validates the specified entity.
    /// </summary>
    /// <param name="entity">The entity.</param>
    private static void Validate(T entity)
    {
      IValidatable validatable = entity as IValidatable;

      if (validatable != null)
      {
        if (!validatable.ValidationResults.IsValid)
        {
          throw new ValidationException(BuildValidationErrors(validatable.ValidationResults));
        }
      }
    }

    /// <summary>
    /// Builds the validation errors based on a set of validation results
    /// </summary>
    /// <param name="validationResults">The validation results.</param>
    /// <returns>A string which details the errors which occured during validation</returns>
    private static string BuildValidationErrors(ValidationResults validationResults)
    {
      StringBuilder errors = new StringBuilder(Resources.ObjectNotValid);

      foreach (ValidationResult validationResult in validationResults)
      {
        errors.Append(Environment.NewLine + "\t" + validationResult.Message);
      }

      return errors.ToString();
    }
  }
}